/* * Copyright (c) 2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ package com.facebook.network.connectionclass; import android.net.TrafficStats; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.SystemClock; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.Nonnull; /** * Class used to read from TrafficStats periodically, in order to determine a ConnectionClass. */ public class DeviceBandwidthSampler { /** * The DownloadBandwidthManager that keeps track of the moving average and ConnectionClass. */ private final ConnectionClassManager mConnectionClassManager; private AtomicInteger mSamplingCounter; private SamplingHandler mHandler; private HandlerThread mThread; private long mLastTimeReading; private static long sPreviousBytes = -1; // Singleton. private static class DeviceBandwidthSamplerHolder { public static final DeviceBandwidthSampler instance = new DeviceBandwidthSampler(ConnectionClassManager.getInstance()); } /** * Retrieval method for the DeviceBandwidthSampler singleton. * @return The singleton instance of DeviceBandwidthSampler. */ @Nonnull public static DeviceBandwidthSampler getInstance() { return DeviceBandwidthSamplerHolder.instance; } private DeviceBandwidthSampler( ConnectionClassManager connectionClassManager) { mConnectionClassManager = connectionClassManager; mSamplingCounter = new AtomicInteger(); mThread = new HandlerThread("ParseThread"); mThread.start(); mHandler = new SamplingHandler(mThread.getLooper()); } /** * Method call to start sampling for download bandwidth. */ public void startSampling() { if (mSamplingCounter.getAndIncrement() == 0) { mHandler.startSamplingThread(); mLastTimeReading = SystemClock.elapsedRealtime(); } } /** * Finish sampling and prevent further changes to the * ConnectionClass until another timer is started. */ public void stopSampling() { if (mSamplingCounter.decrementAndGet() == 0) { mHandler.stopSamplingThread(); addFinalSample(); } } /** * Method for polling for the change in total bytes since last update and * adding it to the BandwidthManager. */ protected void addSample() { long newBytes = TrafficStats.getTotalRxBytes(); long byteDiff = newBytes - sPreviousBytes; if (sPreviousBytes >= 0) { synchronized (this) { long curTimeReading = SystemClock.elapsedRealtime(); mConnectionClassManager.addBandwidth(byteDiff, curTimeReading - mLastTimeReading); mLastTimeReading = curTimeReading; } } sPreviousBytes = newBytes; } /** * Resets previously read byte count after recording a sample, so that * we don't count bytes downloaded in between sampling sessions. */ protected void addFinalSample() { addSample(); sPreviousBytes = -1; } /** * @return True if there are still threads which are sampling, false otherwise. */ public boolean isSampling() { return (mSamplingCounter.get() != 0); } private class SamplingHandler extends Handler { /** * Time between polls in ms. */ static final long SAMPLE_TIME = 1000; static private final int MSG_START = 1; public SamplingHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_START: addSample(); sendEmptyMessageDelayed(MSG_START, SAMPLE_TIME); break; default: throw new IllegalArgumentException("Unknown what=" + msg.what); } } public void startSamplingThread() { sendEmptyMessage(SamplingHandler.MSG_START); } public void stopSamplingThread() { removeMessages(SamplingHandler.MSG_START); } } }